﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CoreRay
{
    public struct Spectrum : IEquatable<Spectrum>
    {
        //Store spectrum samples using separate fields to make a struct of this object
        private float spectrumSample0;
        private float spectrumSample1;
        private float spectrumSample2;

        private static readonly float[] XWeights = { 0.412453f, 0.357580f, 0.180423f };
        private static readonly float[] YWeights = { 0.212671f, 0.715160f, 0.072169f };
        private static readonly float[] ZWeights = { 0.019334f, 0.119193f, 0.950227f };

        public static readonly Spectrum Black = new Spectrum(0.0f);

        public Spectrum(float value)
        {
            this.spectrumSample0 = value;
            this.spectrumSample1 = value;
            this.spectrumSample2 = value;
        }

        public static Spectrum operator -(Spectrum spectrum)
        {
            return Negate(spectrum);
        }

        public static Spectrum Negate(Spectrum spectrum)
        {
            Spectrum result = new Spectrum();

            result.spectrumSample0 = -spectrum.spectrumSample0;
            result.spectrumSample1 = -spectrum.spectrumSample1;
            result.spectrumSample2 = -spectrum.spectrumSample2;

            return result;
        }

        public static Spectrum operator +(Spectrum spectrum1, Spectrum spectrum2)
        {
            return Add(spectrum1, spectrum2);
        }

        public static Spectrum Add(Spectrum spectrum1, Spectrum spectrum2)
        {
            Spectrum result = new Spectrum();

            result.spectrumSample0 = spectrum1.spectrumSample0 + spectrum2.spectrumSample0;
            result.spectrumSample1 = spectrum1.spectrumSample1 + spectrum2.spectrumSample1;
            result.spectrumSample2 = spectrum1.spectrumSample2 + spectrum2.spectrumSample2;

            return result;
        }

        public static Spectrum operator -(Spectrum spectrum1, Spectrum spectrum2)
        {
            return Subtract(spectrum1, spectrum2);
        }

        public static Spectrum Subtract(Spectrum spectrum1, Spectrum spectrum2)
        {
            Spectrum result = new Spectrum();

            result.spectrumSample0 = spectrum1.spectrumSample0 - spectrum2.spectrumSample0;
            result.spectrumSample1 = spectrum1.spectrumSample1 - spectrum2.spectrumSample1;
            result.spectrumSample2 = spectrum1.spectrumSample2 - spectrum2.spectrumSample2;

            return result;
        }

        public static Spectrum operator *(Spectrum spectrum, float value)
        {
            return Multiply(spectrum, value);
        }

        public static Spectrum Multiply(Spectrum spectrum, float value)
        {
            Spectrum result = new Spectrum();

            result.spectrumSample0 = spectrum.spectrumSample0 * value;
            result.spectrumSample1 = spectrum.spectrumSample1 * value;
            result.spectrumSample2 = spectrum.spectrumSample2 * value;

            return result;
        }

        public static Spectrum operator *(Spectrum spectrum, Spectrum spectrum2)
        {
            return Multiply(spectrum, spectrum2);
        }

        public static Spectrum Multiply(Spectrum spectrum1, Spectrum spectrum2)
        {
            Spectrum result = new Spectrum();

            result.spectrumSample0 = spectrum1.spectrumSample0 * spectrum2.spectrumSample0;
            result.spectrumSample1 = spectrum1.spectrumSample1 * spectrum2.spectrumSample1;
            result.spectrumSample2 = spectrum1.spectrumSample2 * spectrum2.spectrumSample2;

            return result;
        }

        public static Spectrum operator /(Spectrum spectrum, float value)
        {
            return Divide(spectrum, value);
        }

        public static Spectrum Divide(Spectrum spectrum, float value)
        {
            Spectrum result = new Spectrum();

            float inverseValue = 1.0f / value;

            result.spectrumSample0 = spectrum.spectrumSample0 * inverseValue;
            result.spectrumSample1 = spectrum.spectrumSample1 * inverseValue;
            result.spectrumSample2 = spectrum.spectrumSample2 * inverseValue;

            return result;
        }

        public static Spectrum operator /(Spectrum spectrum1, Spectrum spectrum2)
        {
            return Divide(spectrum1, spectrum2);
        }

        public static Spectrum Divide(Spectrum spectrum1, Spectrum spectrum2)
        {
            Spectrum result = new Spectrum();

            result.spectrumSample0 = spectrum1.spectrumSample0 / spectrum2.spectrumSample0;
            result.spectrumSample1 = spectrum1.spectrumSample1 / spectrum2.spectrumSample1;
            result.spectrumSample2 = spectrum1.spectrumSample2 / spectrum2.spectrumSample2;

            return result;
        }

        public bool IsBlack()
        {
            return (this.spectrumSample0 == 0.0f && this.spectrumSample1 == 0.0f && this.spectrumSample2 == 0.0f);
        }

        public static Spectrum FromColor(Color color) 
        {
            Spectrum spectrum = new Spectrum();

            spectrum.spectrumSample0 = 3.240479f * color.Red + -1.537150f * color.Green + -0.498535f * color.Blue;
            spectrum.spectrumSample1 = -0.969256f * color.Red + 1.875991f * color.Green + 0.041556f * color.Blue;
            spectrum.spectrumSample2 = 0.055648f * color.Red + -0.204043f * color.Green + 1.057311f * color.Blue;

            return spectrum;
        }

        public Color ToColor()
        {
            float red = 0.0f;
            float green = 0.0f;
            float blue = 0.0f;

            red = XWeights[0] * this.spectrumSample0 + XWeights[1] * this.spectrumSample1 + XWeights[2] * this.spectrumSample2;
            green = YWeights[0] * this.spectrumSample0 + YWeights[1] * this.spectrumSample1 + YWeights[2] * this.spectrumSample2;
            blue = ZWeights[0] * this.spectrumSample0 + ZWeights[1] * this.spectrumSample1 + ZWeights[2] * this.spectrumSample2;

            return new Color(red, green, blue);
        }

        public static bool operator ==(Spectrum spectrum1, Spectrum spectrum2)
        {
            return spectrum1.Equals(spectrum2);
        }

        public static bool operator !=(Spectrum spectrum1, Spectrum spectrum2)
        {
            return !spectrum1.Equals(spectrum2);
        }

        public override bool Equals(object obj)
        {
            return obj is Spectrum ? Equals((Spectrum) obj) : false;
        }

        public override int GetHashCode()
        {
            return this.spectrumSample0.GetHashCode() ^ this.spectrumSample1.GetHashCode() ^ this.spectrumSample2.GetHashCode();
        }

        public bool Equals(Spectrum other)
        {
            return this.spectrumSample0 == other.spectrumSample0 && this.spectrumSample1 == other.spectrumSample1 && this.spectrumSample2 == other.spectrumSample2;
        }
    }
}
